﻿using System.Collections.Generic;
using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace StrategyGame
{
    class Building : IMapObject, IConstructedByCity
	{
		#region Static resources

		private static Texture2D Tech0_FarmTexture;
		private static Texture2D Tech0_HouseTexture;
		private static Texture2D WallCornerTexture;
		private static Texture2D WallHorizontalTexture;
		private static Texture2D WallVerticalTexture;
		private static Texture2D WallHorizontalGateTexture;
		private static Texture2D WallVerticalGateTexture;

		private static Texture2D Tech0_HouseUnderlayTexture;
		private static Texture2D Tech0_FarmUnderlayTexture;

		//textures for teamcolored underlays
		private static Texture2D WallHorizontalUnderlayTexture;
		private static Texture2D WallVerticalUnderlayTexture;
		private static Texture2D WallHorizontalGateUnderlayTexture;
		private static Texture2D WallVerticalGateUnderlayTexture;

		static public void LoadContent(ContentManager content)
		{
			Tech0_FarmTexture = content.Load<Texture2D>("buildings/farm");
			Tech0_HouseTexture = content.Load<Texture2D>("buildings/camp");
			WallCornerTexture = content.Load<Texture2D>("buildings/wall_corner");
			WallHorizontalTexture = content.Load<Texture2D>("buildings/wall_horizontal");
			WallVerticalTexture = content.Load<Texture2D>("buildings/wall_vertical");
			WallHorizontalGateTexture = content.Load<Texture2D>("buildings/wall_gate_horizontal");
			WallVerticalGateTexture = content.Load<Texture2D>("buildings/wall_gate_vertical");

			Tech0_HouseUnderlayTexture = content.Load<Texture2D>("buildings/camp_underlay");
			Tech0_FarmUnderlayTexture = content.Load<Texture2D>("buildings/farm_underlay");

			WallHorizontalUnderlayTexture = content.Load<Texture2D>("buildings/wall_horizontal_underlay");
			WallVerticalUnderlayTexture = content.Load<Texture2D>("buildings/wall_vertical_underlay");
			WallHorizontalGateUnderlayTexture = content.Load<Texture2D>("buildings/wall_gate_horizontal_underlay");
			WallVerticalGateUnderlayTexture = content.Load<Texture2D>("buildings/wall_gate_vertical_underlay");
		}

		#endregion

		#region ConstructionProject subclass for city projects that create buildings

		public class ConstructionProject : IConstructionProject
		{
			#region Wall placement logic (this is huge)

			static bool IsWallCornerAtTile(TileMap map, Point pos)
            {
                Building tile_building = map.BuildingAtTile(pos);

                if (tile_building != null)
                    return tile_building.Type == BuildingType.WallCorner;
                else
                    return false;
            }

            static bool IsTerrainValidForWall(TileMap map, Point pos)
            {
                return map.LookupTile(pos.X, pos.Y) != TileMap.TileType.Ocean;
            }

            static bool CanPlaceWallTile(TileMap map, Point pos)
            {
                bool tile_empty = (map.CityAtTile(pos.X, pos.Y) == null) && (map.BuildingAtTile(pos) == null);
                bool tile_valid = IsTerrainValidForWall(map, pos);

                return tile_empty && tile_valid;
            }

            static void PlaceWallLine(Point start, int length, bool horizontal, City city)
            {
                TileMap map = city.Map;

                Point pos = new Point(start.X, start.Y);
                Point increment;
                if (horizontal)
                    increment = new Point(1, 0);
                else
                    increment = new Point(0, 1);

                int mid_point = length / 2;

                int walls_placed = 0;
                bool last_tile_success = true;

                while (walls_placed <= length)
                {
                    if (CanPlaceWallTile(map, pos))
                    {

                        //for looking backwards: detect nearby wall and join up the tiles smoothly even if it was built previously
                        Point previous_tile = new Point(pos.X - increment.X, pos.Y - increment.Y);

                        //for detecting if we're hitting a dead end, and capping it off with a corner section if so
                        Point next_tile = new Point(pos.X + increment.X, pos.Y + increment.Y);

                        BuildingType type;

                        //place a corner tile if this is the beginning or end of wall
                        if (walls_placed == 0 && !IsWallCornerAtTile(map, previous_tile) //start of a wall, and this isn't a continuation of another wall: place a corner
                            || (!last_tile_success) //last tile was a break in the wall: place a corner
                            || (!CanPlaceWallTile(map, next_tile) && !IsWallCornerAtTile(map, next_tile)) //dead end, and not because it's going into another corner: place a corner
                            || (walls_placed == length && !IsWallCornerAtTile(map, next_tile))) //end of a wall, and this doesn't finish at another wall's corner: place a corner
                            type = BuildingType.WallCorner;
                        else
                        {
                            if (walls_placed == mid_point)
                                type = horizontal ? BuildingType.WallHorizontalGate : BuildingType.WallVerticalGate;
                            else
                                type = horizontal ? BuildingType.WallHorizontalSegment : BuildingType.WallVerticalSegment;
                        }

                        Building finished_building = new Building(type, pos, map);
                        city.AddBuilding(finished_building);

                        last_tile_success = true;
                    }
                    else
                        last_tile_success = false;

                    ++walls_placed;

                    pos.X += increment.X;
                    pos.Y += increment.Y;
                }
            }

			#endregion

			#region Implementation of IConstructionProject

			public void FinishProject(City finished_at_city)
            {
                TileMap map = finished_at_city.Map;
                Point city_pos = finished_at_city.Position;

                Point output_pos = new Point(0, 0);

                switch (Type)
                {
                    //farm: pick a random grass tile in adjacent city
                    case BuildingType.Farm:
                        {
                            Point? nearby_grass = map.RandomTileFromList(finished_at_city.GetTilesAdjacentToBuildings(),
                                                      obj => !obj.BlocksConstruction,
                                                      tile => tile == TileMap.TileType.Grassland,
                                                      finished_at_city.GetMapBoundsRect());

                            //this should theoretically never fail because this is also checked by GetProblems...
                            if (nearby_grass.HasValue)
                                output_pos = nearby_grass.Value;

                            Building finished_building = new Building(Type, output_pos, map);
                            finished_at_city.AddBuilding(finished_building);
                        }
                        break;

                    case BuildingType.Housing:
                        {
                            Point? nearby_land = map.RandomTileFromList(finished_at_city.GetTilesAdjacentToBuildings(),
                                                      obj => !obj.BlocksConstruction,
                                                      tile => tile != TileMap.TileType.Ocean,
                                                      finished_at_city.GetMapBoundsRect());

                            //this should theoretically never fail because this is also checked by GetProblems...
                            if (nearby_land.HasValue)
                                output_pos = nearby_land.Value;

                            Building finished_building = new Building(Type, output_pos, map);
                            finished_at_city.AddBuilding(finished_building);
                        }
                        break;

                    case BuildingType.WallCorner:
                        {
                            Rectangle walls_rect = finished_at_city.GetMapBoundsRect();
                            walls_rect.Inflate(1, 1);

                            PlaceWallLine(new Point(walls_rect.X, walls_rect.Y), walls_rect.Width, true, finished_at_city);
                            PlaceWallLine(new Point(walls_rect.X, walls_rect.Bottom), walls_rect.Width, true, finished_at_city);
                            PlaceWallLine(new Point(walls_rect.X, walls_rect.Y + 1), walls_rect.Height - 2, false, finished_at_city);
                            PlaceWallLine(new Point(walls_rect.Right, walls_rect.Y + 1), walls_rect.Height - 2, false, finished_at_city);
                        }
                        break;

                    default:
                        //error ??
                        break;
                }
            }

            public string[] InfoText
                {
                    get
                    {
                        List<string> result = new List<string>();

                        switch (Type)
                        {
                            case BuildingType.Farm:
                                result.Add("A simple farm, maintained");
                                result.Add("by hand and primitively");
                                result.Add("irrigated.");

                                result.Add(String.Empty);

                                result.Add("+500 food production");
                                break;

                            case BuildingType.Housing:
                                result.Add("Basic single-storey dwellings,");
                                result.Add("built with readily available");
                                result.Add("materials.");

                                result.Add(String.Empty);

                                result.Add("+250 living space");
                                break;
                        }

                        if (GetRequiredWorkers(Type) > 0)
                            result.Add("Uses " +GetRequiredWorkers(Type).ToString() + " workers");
                           
                        return result.ToArray();
                    }
                }

            public string[] GetProblemsBuildingThis(City at_city)
            {
                if (at_city.FreeWorkers < GetRequiredWorkers(Type))
                    return new string[] {"Not enough free workers", "to work at this building"};
                else
                {
                    switch (Type)
                    {
                        //farm: pick a random grass tile in adjacent city
                        case BuildingType.Farm:
                            if (!at_city.Map.RandomTileFromList(at_city.GetTilesAdjacentToBuildings(),
                                    obj => !obj.BlocksConstruction,
                                    tile => tile == TileMap.TileType.Grassland,
                                    at_city.GetMapBoundsRect()).HasValue)
                                return new string[] {"No free grassland tiles"};
                            else
                                break;
                        default:
                            //check for any adjacent land tile
                            if (!at_city.Map.RandomTileFromList(at_city.GetTilesAdjacentToBuildings(),
                                    obj => !obj.BlocksConstruction,
                                    tile => tile != TileMap.TileType.Ocean,
                                    at_city.GetMapBoundsRect()).HasValue)
                                return new string[] { "No free land tiles" };
                            else
                                break;
                    }
                }

                //if we reach this line without returning, there's no problems! so return null
                return null;
            }

            public int ManpowerRequired
            {
                get
                {
                    switch (Type)
                    {
                        case BuildingType.Farm:
                            return 1000;
                        case BuildingType.Housing:
                            return 500;
                        case BuildingType.WallCorner:
                            return 5000;
                        default:
                            return 0;
                    }
                }
            }

            public Texture2D ProjectIcon { get { return TypeToTexture(Type); } }
            public string ProjectName { get { return TypeToString(Type); } }

			#endregion

			public readonly BuildingType Type;

            public ConstructionProject(BuildingType type)
            {
                Type = type;
            }
        }

		#endregion

		#region Building types

		public enum BuildingType
		{
            None,
			Farm,
			Housing,
            WallVerticalSegment,
            WallHorizontalSegment,
            WallVerticalGate,
            WallHorizontalGate,
            WallCorner,
		}
		
		public BuildingType Type { get; private set; }

		#endregion

		#region Building type-specific properties
		
		public static string TypeToString(BuildingType type)
		{
			switch (type)
			{
				case BuildingType.WallCorner: return "Walls";
				case BuildingType.WallHorizontalSegment: return "Walls";
				case BuildingType.WallVerticalSegment: return "Walls";
				default: return type.ToString();
			}
		}

		private static Texture2D TypeToTexture(BuildingType type)
		{
			switch (type)
			{
				case BuildingType.Farm:
					return Tech0_FarmTexture;
				case BuildingType.Housing:
					return Tech0_HouseTexture;
				case BuildingType.WallCorner:
					return WallCornerTexture;
				case BuildingType.WallHorizontalSegment:
					return WallHorizontalTexture;
				case BuildingType.WallVerticalSegment:
					return WallVerticalTexture;
				case BuildingType.WallHorizontalGate:
					return WallHorizontalGateTexture;
				case BuildingType.WallVerticalGate:
					return WallVerticalGateTexture;
				default:
					return null;
			}
		}

		/// <summary>
		/// Look up tech level by building type
		/// </summary>
		/// <param name="type">The building type to look up</param>
		/// <returns>Tech level if the building type has one, otherwise null</returns>
		public static int? TypeToTechLevel(BuildingType type)
		{
			switch (type)
			{
				case BuildingType.Farm:
					return 0;
				case BuildingType.Housing:
					return 0;
				default:
					return null;
			}
		}

		private Texture2D TeamColorUnderlay()
		{
			switch (Type)
			{
				case BuildingType.Housing:
					return Tech0_HouseUnderlayTexture;
				case BuildingType.Farm:
					return Tech0_FarmUnderlayTexture;

				case BuildingType.WallHorizontalGate:
					return WallHorizontalUnderlayTexture;
				case BuildingType.WallVerticalGate:
					return WallVerticalUnderlayTexture;
				case BuildingType.WallHorizontalSegment:
					return WallHorizontalUnderlayTexture;
				case BuildingType.WallVerticalSegment:
					return WallVerticalUnderlayTexture;
				default: return SpriteRectangle.DummyTexture;
			}
		}

		private static int GetRequiredWorkers(BuildingType type)
		{
			switch (type)
			{
				case BuildingType.Farm:
					return 100;
				default:
					return 0;
			}
		}

		#endregion

		#region Public properties

		public string Name { get { return TypeToString(Type); } }

        public int RequiredWorkers { get { return GetRequiredWorkers(Type); } }

		/// <summary>
		/// Tech level of this city
		/// </summary>
		public int? TechLevel { get { return TypeToTechLevel(Type); } }


		#endregion

		#region Rendering

		public void Draw(SpriteBatch sprites, int draw_offset_x, int draw_offset_y, City owner)
		{
			Rectangle draw_rect = Map.GetTileRectangle(Position.X + draw_offset_x, Position.Y + draw_offset_y);

            sprites.Draw(TeamColorUnderlay(), draw_rect, owner.GetNation().NationalColor);
            sprites.Draw(TypeToTexture(Type), draw_rect, Color.White);
		}

		#endregion

		#region Implementation of IConstructedByCity
		public Texture2D ConstructionProjectIcon { get { return TypeToTexture(Type); } }
        public string ConstructionProjectName { get { return Type.ToString(); } }
		#endregion

		#region Implementation of IMapObject

		public TileMap Map { get; private set; }
		public Point Position { get; private set; }

        public bool BlocksUnitMovement
        {
            get
            {
                switch (Type)
                {
                    case BuildingType.WallCorner: return true;
                    case BuildingType.WallVerticalSegment: return true;
                    case BuildingType.WallHorizontalSegment: return true;
                    default: return false;
                }
            }
        }

        public bool BlocksConstruction { get { return true; } }

		#endregion

		public Building(BuildingType type, Point position, TileMap map)
		{
			Type = type;

			Map = map;
            Map.AddMapObject(this);

            Position = new Point(position.X, position.Y);
		}
	}
}
